"""
elio框架APP模块。
"""
import asyncio
import multiprocessing
import os
from queue import Empty
from typing import Optional

import psutil
from prompt_toolkit import Application
from prompt_toolkit.key_binding import KeyBindings, KeyPressEvent
from prompt_toolkit.keys import Keys
from prompt_toolkit.layout import Layout, HSplit, VSplit
from prompt_toolkit.widgets import Button, Frame, Label

from .core import dispatch, get_event_loop
from .logger import logger, LOG_DEFAULT_FORMAT, LOG_DEFAULT_RETENTION
from .observer import AsyncFileEventListener
from .server import start, stop, RpcMethodHookHandler


def _worker(
        quit_event: multiprocessing.Event, performance_data_queue: multiprocessing.Queue, debug: bool = False,
        log_path: Optional[str] = None, log_format: str = LOG_DEFAULT_FORMAT,
        log_retention: str = LOG_DEFAULT_RETENTION, port: int = 8000
):
    """
    在子进程中运行服务端。
    """
    if log_path:
        logger.add(log_path, format=log_format, retention=log_retention, encoding='utf-8', enqueue=True)
    ps = psutil.Process()

    async def collect_performance():
        """
        收集性能数据。
        """
        while True:
            await asyncio.sleep(10)
            performance_data_queue.put((ps.memory_percent(), ps.cpu_percent(), RpcMethodHookHandler.flow,))

    async def check_quit():
        """
        检查主进程是否已经发来了终止信号。
        """
        while True:
            await asyncio.sleep(1)
            if quit_event.is_set():
                await stop()
                exit()

    async def wrapper():
        """
        一个包装函数。
        """
        await asyncio.gather(start(debug=debug, port=port), check_quit(), collect_performance())

    loop = get_event_loop()
    loop.run_until_complete(wrapper())


class ElioApplication:
    """
    使用TAB键切换，回车激活，或使用鼠标操作，h帮助，q退出。
    """
    _app: Application = None
    _file_listeners: set[AsyncFileEventListener] = set()
    _process: Optional[multiprocessing.Process] = None
    _quit_event: multiprocessing.Event = None
    _lHelp: Label = None
    _lStatus: Label = None
    _lCpuPercent: Label = None
    _lMemoryPercent: Label = None
    _lFlow: Label = None
    _bHelp: Button = None
    _bQuit: Button = None
    _bRestart: Button = None
    _bLogShow: Button = None
    _bLogDelete: Button = None

    def __init__(
            self, name: str, directory: str, debug: bool = False, log_path: str = 'logs/elio-default.log',
            log_retention: str = LOG_DEFAULT_RETENTION, log_format: str = LOG_DEFAULT_FORMAT, port: int = 8000
    ) -> None:
        """
        创建APP。
        :param directory: 项目目录。
        :param debug: 调试模式，如果为True会监听directory参数指定的目录，如果有代码文件变化，则自动重启服务端。
        :param log_path: 日志文件路径。
        :param log_retention: 日志保留周期。
        :param log_format: 日志显示格式。
        :param port: GRPC服务端口。
        """
        super().__init__()
        self.debug = debug
        self.log_path = log_path
        self.log_format = log_format
        self.log_retention = log_retention
        self.directory = directory
        self.port = port
        self._lHelp = Label(self.__class__.__doc__)
        self._lStatus = Label('加载中...')
        self._lCpuPercent = Label('CPU数据加载中...')
        self._lMemoryPercent = Label('内存数据加载中...')
        self._lFlow = Label('流量数据加载中...')
        self._bQuit = Button('退出(Q)')
        self._bQuit.control.desc = '退出，快捷键Q，退出' + name
        self._bHelp = Button('帮助(H)')
        self._bHelp.control.desc = '帮助，快捷键H，显示关于' + name + '的帮助'
        self._bRestart = Button('重启(R)')
        self._bRestart.control.desc = '重启，快捷键R，重启' + name + '的服务端'
        self._bLogShow = Button('日志(L)')
        self._bLogShow.control.desc = '显示日志，快捷键L，显示' + name + '的服务日志'
        self._bLogDelete = Button('删除日志(D)')
        self._bLogDelete.control.desc = '删除日志，快捷键D，删除' + name + '的服务日志（会重启服务）'
        kb = KeyBindings()

        @kb.add(Keys.Tab)
        def tab(event: KeyPressEvent):
            """
            tab焦点处理。
            """
            layout = event.app.layout
            layout.focus_next()
            self._lHelp.text = getattr(layout.current_control, 'desc') + '，空格或回车激活'

        @kb.add(Keys.BackTab)
        def back_tab(event: KeyPressEvent):
            """
            tab焦点处理。
            """
            layout = event.app.layout
            layout.focus_previous()
            self._lHelp.text = getattr(layout.current_control, 'desc') + '，空格或回车激活'

        @kb.add('q')
        def quit_(*_):
            """
            退出APP。
            """
            dispatch(self.quit())

        self._bQuit.handler = quit_

        @kb.add('h')
        def help_(*_):
            """
            显示帮助。
            """
            self._lHelp.text = self.__class__.__doc__

        self._bHelp.handler = help_

        @kb.add('r')
        def restart_(*_):
            """
            重启服务端。
            """
            dispatch(self._restart())

        self._bRestart.handler = restart_

        @kb.add('l')
        def log_(*_):
            """
            显示服务端日志。
            """
            os.system(f'notepad "{log_path}"')

        self._bLogShow.handler = log_

        @kb.add('d')
        def log_delete(*_):
            """
            删除服务端日志。
            """
            dispatch(self._delete_log())

        self._bLogDelete.handler = log_delete
        v_shortcuts = VSplit([
            self._bHelp,
            self._bQuit,
            self._bRestart,
            self._bLogShow,
            self._bLogDelete
        ])
        v_performance = VSplit([
            self._lCpuPercent, self._lMemoryPercent, self._lFlow
        ])
        h_split = HSplit([
            v_shortcuts,
            self._lHelp,
            v_performance,
            self._lStatus
        ])
        frame = Frame(h_split, name)
        self._app = Application(Layout(frame, self._bHelp), key_bindings=kb)

    async def run(self):
        """
        运行APP。
        """
        await self._start()
        await self._app.run_async()

    async def quit(self):
        """
        退出APP。
        """
        await self._stop()
        self._app.exit()

    async def _file_listen(self, path: str):
        """
        监听文件改变事件。
        """

        def case(evt):
            """
            内部处理。
            """
            file = evt.dist_path if hasattr(evt, 'dist_path') else evt.src_path
            return file.endswith('.py') or file.endswith('.proto')

        listener = AsyncFileEventListener(path, case)
        self._file_listeners.add(listener)
        async for event in listener:
            await listener.destroy()
            await self._set_status(f'文件 {event.src_path} 以改变，准备重启服务...')
            await asyncio.sleep(1)
            await self._restart()
        await listener.destroy()

    async def _start(self):
        await self._set_status('正在启动服务...')
        self._quit_event = multiprocessing.Event()
        queue = multiprocessing.Queue()
        kwargs = {
            'debug': self.debug,
            'log_path': self.log_path,
            'log_format': self.log_format,
            'log_retention': self.log_retention,
            'port': self.port
        }
        self._process = multiprocessing.Process(target=_worker, args=(self._quit_event, queue,), kwargs=kwargs)
        self._process.start()
        await self._set_status(f'启动服务成功!服务端口： {self.port}')
        self_dir = os.path.dirname(__file__)
        if self.debug:
            dispatch(self._file_listen(self.directory))
            if os.path.abspath(self.directory) != os.path.abspath(self_dir):
                dispatch(self._file_listen(self_dir))

        async def performance_data():
            """
            显示性能数据。
            """
            while self._process:
                await asyncio.sleep(1)
                try:
                    cpu_percent, memory_percent, flow = queue.get(False)
                    network_flow = ('%.2fKB' % (flow / 1024)) if flow > 1024 else \
                        '%dB' % flow
                    self._lCpuPercent.text = 'CPU占用%.2f%%' % (cpu_percent * 100)
                    self._lMemoryPercent.text = '内存占用%.2f%%' % (memory_percent * 100)
                    self._lFlow.text = f'网络流量{network_flow}'
                    self._app.invalidate()
                except Empty:
                    pass

        dispatch(performance_data())

    async def _stop(self):
        await self._set_status('正在停止服务...')
        for listener in self._file_listeners:
            await listener.destroy()
        self._file_listeners.clear()
        if self._process:
            # 先尝试安全方式结束进程
            self._quit_event.set()
            self._quit_event = None
            self._process.join(5)
            if self._process.is_alive():
                # 如果安全方法无法结束进程，则强制结束
                self._process.terminate()
            self._process = None
        await self._set_status('服务已停止！')

    async def _restart(self):
        await self._set_status('准备重启服务...')
        await self._stop()
        await self._start()
        await self._set_status('服务已重启！')

    async def _delete_log(self):
        await self._set_status('准备删除日志...')
        await self._stop()
        os.unlink(self.log_path)
        await self._start()
        await self._set_status('日志已删除！')

    async def _set_status(self, text):
        self._lStatus.text = text
        self._app.invalidate()
        await asyncio.sleep(1)
